package springfixture;

import java.beans.PropertyEditor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyEditorRegistrar;

import springfixture.results.ExceptionResult;
import springfixture.results.Result;
import springfixture.results.RightResult;
import springfixture.results.TextResult;
import springfixture.results.WrongResult;
import fitnesse.fixtures.TableFixture;

/*
 * Copyright 2002-2005 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class SpringFitAdapter {

    private Object springBean;

    private FitBeanWrapperImpl springBeanWrapper;

    private TableFixture tableFixture;

    private Object lastresult;

    private Collection<PropertyEditorRegistrar> propertyEditorRegistrar;

    public SpringFitAdapter(String beanName, TableFixture tableFixture) {
       this( ApplicationContextHolder.getApplicationContextHolder()
                .getApplicationContext().getBean(beanName), 
        tableFixture);
    }
    public SpringFitAdapter(Object bean, TableFixture tableFixture) {
        springBean = bean;
        springBeanWrapper = createBeanWrapper(springBean);
        this.tableFixture = tableFixture;
    }

    private FitBeanWrapperImpl createBeanWrapper(Object bean) {
        FitBeanWrapperImpl result = new FitBeanWrapperImpl(bean);
        propertyEditorRegistrar = ApplicationContextHolder
                .getApplicationContextHolder().getApplicationContext()
                .getBeansOfType(PropertyEditorRegistrar.class).values();

        if (propertyEditorRegistrar != null) {
            for (PropertyEditorRegistrar elem : propertyEditorRegistrar) {
                elem.registerCustomEditors(result);
            }
        }
        return result;
    }

    public Result handleSingleResultMethodCallOrPropertyRead(String methodOrPropertyName,
            Map<String, String> properties) {
        Method method = BeanUtils.findDeclaredMethodWithMinimalParameters(
                springBean.getClass(), methodOrPropertyName);
        if (method == null) {
            return handlePropertyRead(methodOrPropertyName);
        } else {
            return handleMethodCall(properties, method);
        }
    }

    public Result handleComplexResultMethodCall(String methodOrPropertyName,
            Map<String, String> properties) {
        try {
            Method method = BeanUtils.findDeclaredMethodWithMinimalParameters(
                    springBean.getClass(), methodOrPropertyName);
            if (method == null) {
                return new WrongResult(tableFixture,
                        "No method with this name : " + methodOrPropertyName);
            }
            Object[] parameter = convertPropertiesToParameters(method,
                    properties);
            lastresult = method.invoke(springBean, parameter);
            return new RightResult(tableFixture);
        } catch (Exception ex) {
            return new ExceptionResult(tableFixture, ex);
        }
    }

    private Result handleMethodCall(Map properties, Method method) {
        try {
            Object[] parameter = convertPropertiesToParameters(method,
                    properties);
            Object result = method.invoke(springBean, parameter);
            if (result != null) {
                return new TextResult(tableFixture, result.toString());
            } else {
                return new TextResult(tableFixture, null);
            }
        } catch (Exception ex) {
            return new ExceptionResult(tableFixture, ex);
        }
    }

    private Object[] convertPropertiesToParameters(Method method,
            Map<String, String> properties) throws Exception {
        Object[] parameter = null;
        int parameterCount = method.getParameterTypes().length;
        if (parameterCount > 0) {
            parameter = new Object[parameterCount];
            for (int i = 0; i < parameterCount; i++) {
                if (properties.size() == 1) {
                    if (method.getParameterTypes()[i] == String.class) {
                        parameter[i] = properties.values().iterator().next();
                    } else {
                        PropertyEditor propertyEditor = springBeanWrapper
                                .findPropertyEditor(
                                        method.getParameterTypes()[i]);
                        if (propertyEditor != null) {
                            try {
                                propertyEditor.setAsText(properties.values()
                                        .iterator().next());
                                parameter[i] = propertyEditor.getValue();
                            } catch (IllegalArgumentException ex) {

                            }
                        }
                    }
                }
                if (parameter[i] == null) {
                    parameter[i] = method.getParameterTypes()[i].newInstance();
                    BeanWrapper parameterBeanWrapper = createBeanWrapper(parameter[i]);
                    Iterator propertyNames = properties.keySet().iterator();
                    while (propertyNames.hasNext()) {
                        String currentPropertyName = (String) propertyNames
                                .next();
                        if (parameterBeanWrapper
                                .isWritableProperty(currentPropertyName)) {
                            parameterBeanWrapper.setPropertyValue(
                                    currentPropertyName, properties
                                            .get(currentPropertyName));
                        }
                    }
                }
            }
        }

        return parameter;
    }

    private Result handlePropertyRead(String propertyName) {
        if (springBeanWrapper.isReadableProperty(propertyName)) {
            return new TextResult(tableFixture, springBeanWrapper
                    .getPropertyValueAsString(propertyName).toString());
        }
        if (lastresult != null) {
            FitBeanWrapperImpl lastresultWrapper = createBeanWrapper(lastresult);
            if (lastresultWrapper.isReadableProperty(propertyName)) {
                return new TextResult(tableFixture, lastresultWrapper
                        .getPropertyValueAsString(propertyName).toString());
            }
        }
        return new WrongResult(tableFixture,
                "Neither property or method with this name exists : "
                        + propertyName);

    }

}
